package com.clouddms.service.transfer;

import com.clouddms.service.common.IService;
import com.clouddms.service.common.ServiceStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 分块上传管理服务
 * 负责大文件的分块上传、合并和断点续传
 *
 * @author Cloud-DMS Team
 * @version 1.0.0
 */
@Slf4j
@Service
public class ChunkUploadManager implements IService {

    @Value("${clouddms.storage.base-path:uploads/}")
    private String basePath;

    // 分块大小：2MB
    private static final int CHUNK_SIZE = 2 * 1024 * 1024;
    
    // 大文件阈值：10MB
    private static final long LARGE_FILE_THRESHOLD = 10 * 1024 * 1024;
    
    // 上传会话管理
    private final Map<String, UploadSession> uploadSessions = new ConcurrentHashMap<>();
    
    // 临时文件存储路径
    private static final String TEMP_DIR = "temp/chunks/";

    // === IService 接口实现 ===

    @Override
    public String getServiceName() {
        return "chunkUploadManager";
    }

    @Override
    public String getDisplayName() {
        return "分块上传管理";
    }

    @Override
    public String getDescription() {
        return "管理大文件的分块上传、合并和断点续传，提供高效的文件传输能力";
    }

    @Override
    public ServiceStatus getStatus() {
        return ServiceStatus.DEBUGGING;
    }

    @Override
    public int getPriority() {
        return 35;
    }

    // === 分块上传业务方法 ===

    /**
     * 初始化上传会话
     *
     * @param fileName 文件名
     * @param fileSize 文件大小
     * @param userId 用户ID
     * @return 上传会话信息
     */
    public UploadSession initializeUpload(String fileName, long fileSize, Long userId) {
        String uploadId = UUID.randomUUID().toString().replace("-", "");
        
        // 计算分块数量
        int totalChunks = (int) Math.ceil((double) fileSize / CHUNK_SIZE);
        
        UploadSession session = new UploadSession();
        session.setUploadId(uploadId);
        session.setFileName(fileName);
        session.setFileSize(fileSize);
        session.setUserId(userId);
        session.setTotalChunks(totalChunks);
        session.setChunkSize(CHUNK_SIZE);
        session.setCreatedTime(System.currentTimeMillis());
        session.setStatus(UploadStatus.INITIALIZED);
        
        // 创建临时目录
        String tempDir = createTempDirectory(uploadId);
        session.setTempDirectory(tempDir);
        
        uploadSessions.put(uploadId, session);
        
        log.info("初始化上传会话: uploadId={}, fileName={}, fileSize={}, totalChunks={}", 
            uploadId, fileName, fileSize, totalChunks);
        
        return session;
    }

    /**
     * 上传分块
     *
     * @param uploadId 上传ID
     * @param chunkIndex 分块索引（从0开始）
     * @param chunkData 分块数据
     * @return 上传结果
     */
    public ChunkUploadResult uploadChunk(String uploadId, int chunkIndex, byte[] chunkData) {
        UploadSession session = uploadSessions.get(uploadId);
        if (session == null) {
            return ChunkUploadResult.failure("上传会话不存在");
        }

        if (session.getStatus() == UploadStatus.COMPLETED) {
            return ChunkUploadResult.failure("上传已完成");
        }

        if (session.getStatus() == UploadStatus.FAILED) {
            return ChunkUploadResult.failure("上传会话已失败");
        }

        try {
            // 保存分块文件
            String chunkFileName = String.format("chunk_%d", chunkIndex);
            Path chunkPath = Paths.get(session.getTempDirectory(), chunkFileName);
            
            Files.write(chunkPath, chunkData);
            
            // 更新会话状态
            session.markChunkCompleted(chunkIndex);
            session.addUploadedBytes(chunkData.length);
            session.setStatus(UploadStatus.UPLOADING);
            session.setLastUpdateTime(System.currentTimeMillis());

            log.debug("分块上传成功: uploadId={}, chunkIndex={}, size={}", 
                uploadId, chunkIndex, chunkData.length);

            // 检查是否所有分块都已上传
            if (session.isAllChunksCompleted()) {
                // 开始合并文件
                return mergeChunks(session);
            }

            return ChunkUploadResult.success(chunkIndex, session.getUploadProgress());

        } catch (Exception e) {
            log.error("分块上传失败: uploadId={}, chunkIndex={}, error={}", 
                uploadId, chunkIndex, e.getMessage(), e);
            
            session.setStatus(UploadStatus.FAILED);
            return ChunkUploadResult.failure("分块上传失败: " + e.getMessage());
        }
    }

    /**
     * 获取上传进度
     *
     * @param uploadId 上传ID
     * @return 上传进度信息
     */
    public UploadProgress getUploadProgress(String uploadId) {
        UploadSession session = uploadSessions.get(uploadId);
        if (session == null) {
            return null;
        }

        return new UploadProgress(
            uploadId,
            session.getCompletedChunks().size(),
            session.getTotalChunks(),
            session.getUploadedBytes(),
            session.getFileSize(),
            session.getStatus(),
            session.getUploadProgress()
        );
    }

    /**
     * 检查已上传的分块
     *
     * @param uploadId 上传ID
     * @return 已上传的分块列表
     */
    public List<Integer> getUploadedChunks(String uploadId) {
        UploadSession session = uploadSessions.get(uploadId);
        if (session == null) {
            return new ArrayList<>();
        }
        
        return new ArrayList<>(session.getCompletedChunks());
    }

    /**
     * 取消上传
     *
     * @param uploadId 上传ID
     * @return 是否取消成功
     */
    public boolean cancelUpload(String uploadId) {
        UploadSession session = uploadSessions.get(uploadId);
        if (session == null) {
            return false;
        }

        try {
            // 删除临时文件
            cleanupTempFiles(session.getTempDirectory());
            
            // 移除会话
            uploadSessions.remove(uploadId);
            
            log.info("取消上传成功: uploadId={}", uploadId);
            return true;
            
        } catch (Exception e) {
            log.error("取消上传失败: uploadId={}, error={}", uploadId, e.getMessage(), e);
            return false;
        }
    }

    /**
     * 判断是否需要分块上传
     *
     * @param fileSize 文件大小
     * @return 是否需要分块
     */
    public boolean shouldUseChunkedUpload(long fileSize) {
        return fileSize > LARGE_FILE_THRESHOLD;
    }

    /**
     * 获取建议的分块大小
     *
     * @param fileSize 文件大小
     * @return 分块大小
     */
    public int getRecommendedChunkSize(long fileSize) {
        if (fileSize <= 50 * 1024 * 1024) { // 50MB以下
            return 1024 * 1024; // 1MB
        } else if (fileSize <= 500 * 1024 * 1024) { // 500MB以下
            return 2 * 1024 * 1024; // 2MB
        } else {
            return 5 * 1024 * 1024; // 5MB
        }
    }

    // === 私有方法 ===

    /**
     * 合并分块文件
     */
    private ChunkUploadResult mergeChunks(UploadSession session) {
        try {
            log.info("开始合并分块文件: uploadId={}", session.getUploadId());
            
            session.setStatus(UploadStatus.MERGING);
            
            // 创建最终文件路径
            String userDir = createUserDirectory(session.getUserId());
            String finalFileName = UUID.randomUUID().toString().replace("-", "") + 
                getFileExtension(session.getFileName());
            Path finalPath = Paths.get(userDir, finalFileName);
            
            // 合并分块
            try (FileOutputStream fos = new FileOutputStream(finalPath.toFile())) {
                for (int i = 0; i < session.getTotalChunks(); i++) {
                    String chunkFileName = String.format("chunk_%d", i);
                    Path chunkPath = Paths.get(session.getTempDirectory(), chunkFileName);
                    
                    if (!Files.exists(chunkPath)) {
                        throw new IOException("分块文件不存在: " + chunkFileName);
                    }
                    
                    try (FileInputStream fis = new FileInputStream(chunkPath.toFile())) {
                        byte[] buffer = new byte[8192];
                        int bytesRead;
                        while ((bytesRead = fis.read(buffer)) != -1) {
                            fos.write(buffer, 0, bytesRead);
                        }
                    }
                }
            }
            
            // 更新会话状态
            session.setStatus(UploadStatus.COMPLETED);
            session.setFinalFilePath(finalPath.toString());
            session.setCompletedTime(System.currentTimeMillis());
            
            // 清理临时文件
            cleanupTempFiles(session.getTempDirectory());
            
            log.info("分块文件合并成功: uploadId={}, finalPath={}", 
                session.getUploadId(), finalPath);
            
            return ChunkUploadResult.completed(session.getUploadId(), finalPath.toString());
            
        } catch (Exception e) {
            log.error("合并分块文件失败: uploadId={}, error={}", 
                session.getUploadId(), e.getMessage(), e);
            
            session.setStatus(UploadStatus.FAILED);
            return ChunkUploadResult.failure("文件合并失败: " + e.getMessage());
        }
    }

    /**
     * 创建临时目录
     */
    private String createTempDirectory(String uploadId) {
        try {
            Path tempPath = Paths.get(basePath, TEMP_DIR, uploadId);
            Files.createDirectories(tempPath);
            return tempPath.toString();
        } catch (Exception e) {
            log.error("创建临时目录失败: {}", e.getMessage(), e);
            throw new RuntimeException("无法创建临时目录", e);
        }
    }

    /**
     * 创建用户目录
     */
    private String createUserDirectory(Long userId) {
        try {
            String userDir = basePath + "users" + File.separator + "user_" + userId;
            Path userPath = Paths.get(userDir);
            if (!Files.exists(userPath)) {
                Files.createDirectories(userPath);
            }
            return userDir;
        } catch (Exception e) {
            log.error("创建用户目录失败: {}", e.getMessage(), e);
            throw new RuntimeException("无法创建用户目录", e);
        }
    }

    /**
     * 清理临时文件
     */
    private void cleanupTempFiles(String tempDirectory) {
        try {
            Path tempPath = Paths.get(tempDirectory);
            if (Files.exists(tempPath)) {
                Files.walk(tempPath)
                    .filter(Files::isRegularFile)
                    .forEach(file -> {
                        try {
                            Files.delete(file);
                        } catch (Exception e) {
                            log.warn("删除临时文件失败: {}", file, e);
                        }
                    });
                Files.deleteIfExists(tempPath);
            }
        } catch (Exception e) {
            log.warn("清理临时目录失败: {}", tempDirectory, e);
        }
    }

    /**
     * 获取文件扩展名
     */
    private String getFileExtension(String fileName) {
        int lastDotIndex = fileName.lastIndexOf('.');
        return lastDotIndex > 0 ? fileName.substring(lastDotIndex) : "";
    }

    /**
     * 清理过期的上传会话
     */
    public void cleanupExpiredSessions() {
        long currentTime = System.currentTimeMillis();
        long expirationTime = 24 * 60 * 60 * 1000; // 24小时
        
        uploadSessions.entrySet().removeIf(entry -> {
            UploadSession session = entry.getValue();
            if (currentTime - session.getLastUpdateTime() > expirationTime) {
                try {
                    cleanupTempFiles(session.getTempDirectory());
                    log.info("清理过期上传会话: uploadId={}", entry.getKey());
                    return true;
                } catch (Exception e) {
                    log.warn("清理过期会话失败: uploadId={}", entry.getKey(), e);
                }
            }
            return false;
        });
    }
}